<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://www.zjzit.cn>
// +----------------------------------------------------------------------
namespace App\Service;

// 暂未使用
class _Upload
{
    /**
     * 上传文件根目录
     * @var string
     */
    private $rootPath;


    /**
     * 默认上传配置
     * @var array
     */
    private $config = array(
        'mimes'        => array(), //允许上传的文件MiMe类型
        //'maxSize'      => 0, //上传的文件大小限制 (0-不做限制)
        'exts'         => array(), //允许上传的文件后缀
        'autoSub'      => true, //自动子目录保存文件
        //'subName'      => array('date', 'Y-m-d'), //子目录创建方式，[0]-函数名，[1]-参数，多个参数使用数组
        //'rootPath'     => './Uploads/', //保存根路径
        'savePath'     => '', //保存路径
        'saveName'     => array('uniqid', ''), //上传文件命名规则，[0]-函数名，[1]-参数，多个参数使用数组
        'saveExt'      => '', //文件保存后缀，空则使用原后缀
        'replace'      => false, //存在同名是否覆盖
        'hash'         => true, //是否生成hash编码
        'callback'     => false, //检测文件是否存在回调，如果存在返回文件信息数组
        'driver'       => '', // 文件上传驱动
        'driverConfig' => array(), // 上传驱动配置

        'maxSize'  => 50 * 1024 * 1024,  // 上传的文件大小限制 (0-不做限制)
        'rootPath' => './storage',
        'subName'  => 'images/', //date('Y/m/d'),
    );

    /**
     * 上传错误信息
     * @var string
     */
    private $error = ''; //上传错误信息

    /**
     * 构造方法，用于构造上传实例
     * @param array $config 配置
     * @param string $driver 要使用的上传驱动 LOCAL-本地上传驱动，FTP-FTP上传驱动
     */
    public function __construct()
    {
        /* 调整配置，把字符串配置参数转换为数组 */
        if(!empty($this->config['mimes'])){
            if(is_string($this->mimes)){
                $this->config['mimes'] = explode(',', $this->mimes);
            }
            $this->config['mimes'] = array_map('strtolower', $this->mimes);
        }
        if(!empty($this->config['exts'])){
            if(is_string($this->exts)){
                $this->config['exts'] = explode(',', $this->exts);
            }
            $this->config['exts'] = array_map('strtolower', $this->exts);
        }
    }

    /**
     * 使用 $this->name 获取配置
     * @param string $name 配置名称
     */
    public function __get($name)
    {
        return $this->config[$name];
    }

    public function __set($name, $value)
    {
        if(isset($this->config[$name])){
            $this->config[$name] = $value;
        }
    }

    public function __isset($name)
    {
        return isset($this->config[$name]);
    }

    /**
     * 获取最后一次上传错误信息
     * @return string 错误信息
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * 上传单个文件
     * @param array $file 文件数组
     * @return array        上传成功后的文件信息
     */
    public function uploadOne($file)
    {
        $info = $this->upload(array($file));

        return $info ? $info[0] : $info;
    }

    /**
     * 上传文件
     * @param 文件信息数组 $files ，通常是 $_FILES数组
     */
    public function upload($files = '')
    {
        if('' === $files){
            $files = $_FILES;
        }
        if(empty($files)){
            $this->error = '没有上传的文件！';

            return false;
        }

        /* 检测上传根目录 */
        if(!$this->checkRootPath($this->config['rootPath'])){
            return false;
        }

        /* 检查上传目录 */
        if(!$this->checkSavePath($this->config['savePath'])){
            return false;
        }

        /* 逐个检测并上传文件 */
        $info = array();
        if(function_exists('finfo_open')){
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
        }
        // 对上传文件数组信息处理
        $files = $this->dealFiles($files);
        foreach($files as $key => $file){
            $file['name'] = strip_tags($file['name']);
            if(!isset($file['key'])) $file['key'] = $key;
            /* 通过扩展获取文件类型，可解决FLASH上传$FILES数组返回文件类型错误的问题 */
            if(isset($finfo)){
                $file['type'] = finfo_file($finfo, $file['tmp_name']);
            }

            /* 获取上传文件后缀，允许上传无后缀文件 */
            $file['ext'] = pathinfo($file['name'], PATHINFO_EXTENSION);

            /* 文件上传检测 */
            if(!$this->check($file)){
                continue;
            }

            /* 获取文件hash */
            if($this->config['hash']){
                $file['md5']  = md5_file($file['tmp_name']);
                $file['sha1'] = sha1_file($file['tmp_name']);
            }

            /* 生成保存文件名 */
            $savename = $this->getSaveName($file);
            if(false == $savename){
                continue;
            } else{
                $file['savename'] = $savename;
            }

            /* 检测并创建子目录 */
            $subpath = $this->getSubPath($file['name']);
            if(false === $subpath){
                continue;
            } else{
                $file['savepath'] = $this->config['savePath'].$subpath;
            }

            /* 对图像文件进行严格检测 */
            $ext = strtolower($file['ext']);
            if(in_array($ext, array('gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf'))){
                $imginfo = getimagesize($file['tmp_name']);
                if(empty($imginfo) || ($ext == 'gif' && empty($imginfo['bits']))){
                    $this->error = '非法图像文件！';
                    continue;
                }
            }

            /* 保存文件 并记录保存成功的文件 */
            if($this->save($file, $this->replace)){
                unset($file['error'], $file['tmp_name']);
                $info[$key] = $file;
            }
        }
        if(isset($finfo)){
            finfo_close($finfo);
        }

        return empty($info) ? false : $info;
    }

    /**
     * 转换上传文件数组变量为正确的方式
     * @access private
     * @param array $files 上传的文件变量
     * @return array
     */
    private function dealFiles($files)
    {
        $fileArray = array();
        $n         = 0;
        foreach($files as $key => $file){
            if(is_array($file['name'])){
                $keys  = array_keys($file);
                $count = count($file['name']);
                for($i = 0; $i < $count; $i++){
                    $fileArray[$n]['key'] = $key;
                    foreach($keys as $_key){
                        $fileArray[$n][$_key] = $file[$_key][$i];
                    }
                    $n++;
                }
            } else{
                $fileArray = $files;
                break;
            }
        }

        return $fileArray;
    }


    /**
     * 检查上传的文件
     * @param array $file 文件信息
     */
    private function check($file)
    {
        /* 文件上传失败，捕获错误代码 */
        if($file['error']){
            $this->error($file['error']);

            return false;
        }

        /* 无效上传 */
        if(empty($file['name'])){
            $this->error = '未知上传错误！';
        }

        /* 检查是否合法上传 */
        if(!is_uploaded_file($file['tmp_name'])){
            $this->error = '非法上传文件！';

            return false;
        }

        /* 检查文件大小 */
        if(!$this->checkSize($file['size'])){
            $this->error = '上传文件大小不符！';

            return false;
        }

        /* 检查文件Mime类型 */
        //TODO:FLASH上传的文件获取到的mime类型都为application/octet-stream
        if(!$this->checkMime($file['type'])){
            $this->error = '上传文件MIME类型不允许！';

            return false;
        }

        /* 检查文件后缀 */
        if(!$this->checkExt($file['ext'])){
            $this->error = '上传文件后缀不允许';

            return false;
        }

        /* 通过检测 */

        return true;
    }


    /**
     * 获取错误代码信息
     * @param string $errorNo 错误号
     */
    private function error($errorNo)
    {
        switch($errorNo){
            case 1:
                $this->error = '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值！';
                break;
            case 2:
                $this->error = '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值！';
                break;
            case 3:
                $this->error = '文件只有部分被上传！';
                break;
            case 4:
                $this->error = '没有文件被上传！';
                break;
            case 6:
                $this->error = '找不到临时文件夹！';
                break;
            case 7:
                $this->error = '文件写入失败！';
                break;
            default:
                $this->error = '未知上传错误！';
        }
    }

    /**
     * 检查文件大小是否合法
     * @param integer $size 数据
     */
    private function checkSize($size)
    {
        return !($size > $this->maxSize) || (0 == $this->maxSize);
    }

    /**
     * 检查上传的文件MIME类型是否合法
     * @param string $mime 数据
     */
    private function checkMime($mime)
    {
        return empty($this->config['mimes']) ? true : in_array(strtolower($mime), $this->mimes);
    }

    /**
     * 检查上传的文件后缀是否合法
     * @param string $ext 后缀
     */
    private function checkExt($ext)
    {
        return empty($this->config['exts']) ? true : in_array(strtolower($ext), $this->exts);
    }

    /**
     * 根据上传文件命名规则取得保存文件名
     * @param string $file 文件信息
     */
    private function getSaveName($file)
    {
        $rule = $this->config['saveName'];
        if(empty($rule)){ //保持文件名不变
            /* 解决pathinfo中文文件名BUG */
            $filename = substr(pathinfo("_{$file['name']}", PATHINFO_FILENAME), 1);
            $savename = $filename;
        } else{
            $savename = $this->getName($rule, $file['name']);
            if(empty($savename)){
                $this->error = '文件命名规则错误！';

                return false;
            }
        }

        /* 文件保存后缀，支持强制更改文件后缀 */
        $ext = empty($this->config['saveExt']) ? $file['ext'] : $this->saveExt;

        return $savename.'.'.$ext;
    }

    /**
     * 获取子目录的名称
     * @param array $file 上传的文件信息
     */
    private function getSubPath($filename)
    {
        $subpath = '';
        $rule    = $this->config['subName'];
        if($this->config['autoSub'] && !empty($rule)){
            $subpath = '/'.$this->getName($rule, $filename).'/';
            if(!empty($subpath)){
                $subpath = $subpath.date('Ymd').'/';
                if(!$this->mkdir($this->config['savePath'].$subpath)){
                    return false;
                }
            }
        }

        return $subpath;
    }

    /**
     * 根据指定的规则获取文件或目录名称
     * @param array $rule 规则
     * @param string $filename 原文件名
     * @return string           文件或目录名称
     */
    private function getName($rule, $filename)
    {
        $name = '';
        if(is_array($rule)){ //数组规则
            $func  = $rule[0];
            $param = (array)$rule[1];
            foreach($param as &$value){
                $value = str_replace('__FILE__', $filename, $value);
            }
            $name = call_user_func_array($func, $param);
        } elseif(is_string($rule)){ //字符串规则
            if(function_exists($rule)){
                $name = call_user_func($rule);
            } else{
                $name = $rule;
            }
        }

        //$name = date('Y/m/d').$name;

        return $name;
    }

    /**
     * 检测上传根目录
     * @param string $rootpath 根目录
     * @return boolean true-检测通过，false-检测失败
     */
    public function checkRootPath($rootpath)
    {
        if(!(is_dir($rootpath) && is_writable($rootpath))){
            $this->error = '上传根目录不存在！请尝试手动创建:'.$rootpath;

            return false;
        }
        $this->rootPath = $rootpath;

        return true;
    }

    /**
     * 检测上传目录
     * @param string $savepath 上传目录
     * @return boolean          检测结果，true-通过，false-失败
     */
    public function checkSavePath($savepath)
    {
        /* 检测并创建目录 */
        if(!$this->mkdir($savepath)){
            return false;
        } else{
            /* 检测目录是否可写 */
            if(!is_writable($this->rootPath.$savepath)){
                $this->error = '上传目录 '.$savepath.' 不可写！';

                return false;
            } else{
                return true;
            }
        }
    }

    /**
     * 保存指定文件
     * @param array $file 保存的文件信息
     * @param boolean $replace 同名文件是否覆盖
     * @return boolean          保存状态，true-成功，false-失败
     */
    public function save($file, $replace = true)
    {
        $filename = $this->rootPath.$file['savepath'].$file['savename'];

        /* 不覆盖同名文件 */
        if(!$replace && is_file($filename)){
            $this->error = '存在同名文件'.$file['savename'];

            return false;
        }

        /* 移动文件 */
        if(!move_uploaded_file($file['tmp_name'], $filename)){
            $this->error = '文件上传保存错误！';

            return false;
        }

        return true;
    }

    /**
     * 创建目录
     * @param string $savepath 要创建的穆里
     * @return boolean          创建状态，true-成功，false-失败
     */
    public function mkdir($savepath)
    {
        $dir = $this->rootPath.$savepath;
        if(is_dir($dir)){
            return true;
        }

        if(mkdir($dir, 0777, true)){
            return true;
        } else{
            $this->error = "目录 {$savepath} 创建失败！";

            return false;
        }
    }
}

